package com.yuzhi.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.csv.CsvUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yuzhi.system.domain.Area;
import com.yuzhi.system.domain.AreaShort;
import com.yuzhi.system.service.IAreaService;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class SysAreaServiceImpl implements IAreaService {

    private Map<String, Area> allAreasMap;
    private Map<String, List<Area>> allAreasNameMap;
    private Map<String, List<Area>> allExtAreasNameMap;
    private Map<String, List<Area>> allAreasPidMap;
    private List<Area> allProvinces;
    private List<AreaShort> allShortProvinces;
    private List<Area> allCities;
    private List<Area> allDistrict;

    public Map<String, Area> getAllAreasMap() {
        return allAreasMap;
    }

    @Override
    public List<AreaShort> getAllProvinces() {
        return allShortProvinces;
    }

    @Autowired
    private ObjectMapper objectMapper;

    @PostConstruct
    @SneakyThrows
    protected void loadingToCache() {
        String content = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/area/areas.json")), StandardCharsets.UTF_8);
        allProvinces = objectMapper.readValue(content, new TypeReference<>() {});
        allShortProvinces = objectMapper.readValue(content, new TypeReference<>() {});

        allCities = allProvinces.stream().flatMap(province -> province.getChildren().stream()).collect(Collectors.toList());
        allDistrict = allCities.stream().flatMap(city -> city.getChildren().stream()).collect(Collectors.toList());

        List<Area> allAreas = new ArrayList<>();
        allAreas.addAll(allProvinces);
        allAreas.addAll(allCities);
        allAreas.addAll(allDistrict);
        allAreas = allAreas.stream().distinct().collect(Collectors.toList());

        allAreasMap = allAreas.stream().collect(Collectors.toMap(Area::getCode, Function.identity()));
        allAreasNameMap = allAreas.stream().collect(Collectors.groupingBy(Area::getName));
        allExtAreasNameMap = allAreas.stream().collect(Collectors.groupingBy(Area::getExtName));
        allAreasPidMap = allAreas.stream().collect(Collectors.groupingBy(Area::getPid));
        Map<String, String> geoMap = new HashMap<>(allAreas.size());
        //set geo
        CsvUtil.getReader().read(IOUtils.toBufferedReader(new InputStreamReader(Objects.requireNonNull(this.getClass().getResourceAsStream("/area/ok_geo.csv"))))).forEach(row -> {
            String id = row.get(0);
            String geo = row.get(5);
            id = growup(id);
            if (!geo.contains("EMPTY")) {
                geoMap.put(id, geo);
            }
        });

        allAreas.forEach(area -> {
            if (CollUtil.isNotEmpty(area.getChildren())) {
                area.getChildren().forEach(child -> child.setParent(area));
            }
            String geo = geoMap.get(area.getCode());
            if (CharSequenceUtil.isNotBlank(geo) && geo.split(" ").length == 2) {
                area.setLongitude(geo.split(" ")[0]);
                area.setLatitude(geo.split(" ")[1]);
            }
        });

        allAreas.clear();

    }

    private String growup(String id) {
        if (id.length() == 2) {
            id = id.concat("0000");
        } else if (id.length() == 4) {
            id = id.concat("00");
        }
        return id;
    }

    @Override
    public List<Area> getProvinces() {
        return allProvinces;
    }

    @Override
    public List<Area> getCities() {
        return allCities;
    }

    @Override
    public List<String> getSelfAndSonCodes(String areaCode) {
        Area area = getByCode(areaCode);
        return getSelfAndSonCodes(area);
    }

    @Override
    public Area getByName(String areaName, Integer level) {
        List<Area> areas = allAreasNameMap.get(areaName);

        if (CollUtil.isEmpty(areas)) {
            areas = allExtAreasNameMap.get(areaName);
        }

        if (CollUtil.isEmpty(areas) && withDistrictSuffix(areaName)) {
            areaName = areaName.substring(0, areaName.length() - 1);
            areas = allAreasNameMap.get(areaName);
        }

        if (CollUtil.isEmpty(areas)) {
            return null;
        }

        if (areas.size() == 1) {
            return areas.get(0);
        }

        return areas.stream().filter(area -> area.getLevel() == level).findAny().orElse(null);
    }

    private boolean withDistrictSuffix(String areaName) {
        if (CharSequenceUtil.isNotBlank(areaName)) {
            for (String suffix : Arrays.asList("省", "市", "区", "县")) {
                if (areaName.endsWith(suffix)) {
                    return true;
                }
            }
        }
        return false;
    }

    private List<String> getSelfAndSonCodes(Area area) {
        if (area != null) {
            List<String> areaCodes = new ArrayList<>();
            areaCodes.add(area.getCode());
            List<Area> sons = area.getChildren();
            if (CollUtil.isNotEmpty(sons)) {
                sons.forEach(son -> areaCodes.addAll(getSelfAndSonCodes(son)));
            }
            return areaCodes;
        }
        return Collections.emptyList();
    }

    @Override
    public Map<String, Area> getAsMap(List<String> areaCodes) {
        if (!CollectionUtils.isEmpty(areaCodes)) {
            Map<String, Area> map = new HashMap<>(areaCodes.size() * 2);
            for (String areaCode : areaCodes) {
                Area area = allAreasMap.get(areaCode);
                if (area != null) {
                    map.put(areaCode, area);
                }
            }
            return map;
        } else {
            return Collections.emptyMap();
        }
    }

    @Override
    public Area getByCode(String areaCode) {
        return allAreasMap.get(areaCode);
    }

    @Override
    public List<Area> getByCodes(List<String> areaCodes) {
        if (CollUtil.isEmpty(areaCodes)) {
            return Collections.emptyList();
        }
        return areaCodes.stream().map(this::getByCode).filter(Objects::nonNull).collect(Collectors.toList());
    }

    @Override
    public List<Area> getByCodes(String... areaCodes) {
        return getByCodes(List.of(areaCodes));
    }

    @Override
    public String getCodeByExtName(String extName) {
        Area area = this.getByName(extName, 2);
        if (Objects.nonNull(area)) {
            return area.getCode();
        }
        return CharSequenceUtil.EMPTY;
    }

    @Override
    public String getCompleteInfoByCodeData(String areaCode) {
        // code代表的本身行政区
        Area area = this.allAreasMap.get(areaCode);
        if (ObjectUtil.isNotNull(area)) {
            // 层级
            int level = area.getLevel();
            List<String> list = new ArrayList<>();
            list.add(area.getExtName());
            // 父级id
            String pidStr = area.getPid();
            for (int i = 0; i < level; i++) {
                String upperCode;
                if (pidStr.length() == 4) {
                    upperCode = CharSequenceUtil.join("", pidStr, "00");
                    Area upper = this.allAreasMap.get(upperCode);
                    pidStr = upper.getPid();
                    list.add(upper.getExtName());
                }
                if (pidStr.length() == 2) {
                    upperCode = CharSequenceUtil.join("", pidStr, "0000");
                    Area upper = this.allAreasMap.get(upperCode);
                    list.add(upper.getExtName());
                    break;
                }
            }
            return String.join("", CollUtil.reverse(list));
        }
        return CharSequenceUtil.EMPTY;
    }

    @Override
    public List<Area> getOnlyProvinces() {
        return allProvinces.stream().map(p -> p.setChildren(null)).collect(Collectors.toList());

    }

    @Override
    public List<Area> getCityAndDistrictByProvince(String provinceCode) {
        return allAreasPidMap.get(CharSequenceUtil.sub(provinceCode,0,2));
    }
}
